import { isKeyboardEvent, isMouseEvent } from '../helper/asset'
import { hasOverlap } from '../helper/shape'
import { EventMap } from '../module/event'
import { Point2D } from '../point'
import { Shape } from '../shape'

export class Canvas {
  canvas: HTMLCanvasElement
  ctx: CanvasRenderingContext2D | null
  shapes: Map<string, Shape> = new Map()

  constructor(width = 600, height = 500) {
    this.canvas = document.createElement('canvas')
    this.canvas.width = width
    this.canvas.height = height
    this.canvas.tabIndex = 0

    this.ctx = this.canvas.getContext('2d')

    this.initialEvent()
  }

  add(shape: Shape) {
    if (this.ctx === null) return
    shape.draw(this.ctx)
    this.shapes.set(shape.id, shape)
  }

  update() {
    if (this.ctx === null || !Shape.update) return

    const updateShape = this.shapes.get(Shape.update)

    if (updateShape) {
      const { left, top, width, height } = updateShape.boundingRect
      // 开始更新
      this.ctx.save()
      this.ctx.clearRect(left, top, width, height)
      this.ctx.rect(left, top, width, height)
      this.ctx.clip()

      // 过滤相交shape
      for (const shape of this.shapes.values()) {
        if (hasOverlap(updateShape.boundingRect, shape.boundingRect)) {
          shape.draw(this.ctx)
        }
      }
      this.ctx.restore()
      updateShape.draw(this.ctx)
      Shape.update = null
    }
  }

  private initialEvent() {
    this.canvas.addEventListener('click', this.handleEvent('click'))
    this.canvas.addEventListener('mousedown', this.handleEvent('mousedown'))
    this.canvas.addEventListener('mousemove', this.handleEvent('mousemove'))
    this.canvas.addEventListener('mouseup', this.handleEvent('mouseup'))
    this.canvas.addEventListener('keydown', this.handleEvent('keydown'))
  }

  private handleEvent =
    <T extends keyof EventMap>(name: T) =>
    (event: EventMap[T]) => {
      this.shapes.forEach((shape) => {
        const listeners = shape.listenerMap.get(name)

        if (!listeners) return

        if (isMouseEvent(name, event)) {
          if (shape.isPointInClosedRegion(new Point2D(event.clientX, event.clientY))) {
            listeners.forEach((listener) => listener(event))
          }
        }

        if (isKeyboardEvent(name, event)) {
          listeners.forEach((listener) => listener(event))
        }
      })
    }
}
